博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Libimseti推荐系统
阅读量:6423 次
发布时间:2019-06-23

本文共 13897 字,大约阅读时间需要 46 分钟。

技术:easyUI、jQuery、Spring、Struts、Hibernate、Mahout、MySQL

本Libimseti推荐系统使用数据、代码參考《Mahout in action》第五章内容。

系统能够从这里下载: 或  http://pan.baidu.com/s/1nvzqWcx (包括源代码)。

1. 系统部署

1.1 数据库

(1)改动Configuration文件夹中的db.properties中的数据库配置;
(2)从http://pan.baidu.com/s/1bOzCtC 下载所须要的数据,解压后能够看到gender.dat 和ratings.dat文件;
(3)启动project,自己主动生成相关表。
(4)在数据库中执行src文件夹下*.sql。导入相关数据;

1.2 公共配置

(1)改动src文件夹下com.fz.util.Utils中的genderFile和ratingsFile变量为正确文件地址。

2. 系统功能

2.1 Libimseti推荐

启动tomcat,訪问http://localhost:8080/rec 就可以訪问系统主页。例如以下:

2.1.1 用户评分档案查询

在推荐算法页面点击”查询”按钮。就可以依据用户ID输入框里面的用户ID查询用户对其它档案的评分,同一时候这里把用户的性别和档案的性别一起查出来了。

这里显示使用的是easyUI的datagrid。其后台代码例如以下:
$('#ratingId').datagrid({		border:false,	//	fitColumns:true,		singleSelect:true,	//	width:600,		height:200,		nowrap:false,	//	fit:true,		pagination:true,//分页控件		pageSize:4,  // 每页记录数,须要和pageList保持倍数关系		pageList:[4,8,12],		rownumbers:true,//行号	//	pagePosition:'bottom',		url:'rating/rating_getRatingData.action',		queryParams: {			uid: userIdValue,			selectgender:selectGender		},		toolbar: "#toolbar",		columns:[[			{field:'id',title:'用户ID',width:'50'},			{field:'gender',title:'用户Gender',width:'80'},			{field:'itemId',title:'档案ID',width:'120'},			{field:'pref',title:'档案评分',width:'150'},			{field:'itemGender',title:'档案Gender',width:'100'},			{field:'desc',title:'档案描写叙述',width:'200',},		]]	});
使用了toolbar。提供“加入”、“改动”和“删除“功能,使用分页组件用于分页显示查询数据。因为用户评分数据和用户性别数据是在两个表中,所以新建了一个中间类UserRating用于组装数据传入前台,代码例如以下:
public Map
getRatingByUId(Integer uId,int rows,int page, char selectgender){ String hql = "from Rating r where UID="+uId +" order by r.uId,r.itemId"; String hqlCount ="select count(1) from Rating where UID="+uId; String hqlGender = "from Gender where UID="+uId; List
ratings = baseDAO.find(hql,new Object[]{},page,rows); List
userRatings = new ArrayList
(); if(ratings.size()<=0){ return null; } // 获取用户Gender // List
gender =genderDAO.find(hqlGender); char uGender = genderDAO.find(hqlGender).get(0).getGender(); char itemGender; UserRating ur = null; for(Rating rating:ratings){ ur= new UserRating(); ur.setId(uId); ur.setDesc(rating.getDesc()); ur.setItemId(rating.getItemId()); ur.setPref(rating.getPref()); ur.setGender(uGender); // 获取ITEM gender hqlGender="from Gender g where UID="+rating.getItemId(); itemGender =genderDAO.find(hqlGender).get(0).getGender(); ur.setItemGender(itemGender); userRatings.add(ur); } Map
jsonMap = new HashMap
(); jsonMap.put("total", baseDAO.count(hqlCount)); jsonMap.put("rows", userRatings); return jsonMap; }
这里的selectgender变量,本来是在页面加入的一个用于在查询时过滤性别的变量,后面感觉有点麻烦就没做了(性别数据在gender表。分页针对的是rating表);

2.1.2 用户加入对其它未评分档案数据

用户加入对其它未评分档案数据的页面例如以下(点击toolbar中的”加入“按钮):
这里使用的easyUI的window组件,打开页面后依据用户的信息先初始化用户ID和用户性别两个性别,且不可改动。用户须要输入档案ID(必选项)、档案性别和档案描写叙述;
用户输入档案ID的时候,使用ajax实时向后台发送消息。查询用户是否对档案ID已经评分过。假设评分过就进行如图的提示,此功能首先对validatebox进行扩展。然后使用Validator的框架进行验证。代码例如以下:
// 用户在添加对其它项目评分的时候,须要检查是否该项目用户已经评过分 $.extend($.fn.validatebox.defaults.rules, {	hasItem : {		validator : function(value,param) {			var uid = $('#uidId').val();			console.info("value:"+value+",user:"+uid);						return hasItem(uid,value);		},		message : '用户已对该项目评过分!'	}});// 检查用户是否对项目评过分function hasItem(user,item){	if(isNaN(parseInt(item))){		return false;	}	var boolean =false;	$.ajax({ // 获取数据		url : "rating/rating_hasItem.action",		data : "uid=" + user+"&itemid="+item,		dataType : "json",		async:false,		success : function(data) {			console.info("用户"+user+"是否对项目"+item+"评分?

"+data); // 设置 if(data==false||data=="false"){ boolean=true; } } }); return boolean; }

这样在jsp页面就能够简单的使用以下的代码就可以:

2.1.3 用户改动当前档案信息

改动用户当前档案信息界面例如以下:
当中的用户ID和档案ID是不可改动的;这里弹出的window和加入功能界面的window是一样的,这里在弹出界面的时候改动其title。

2.1.4 删除用户对当前档案数据

删除用户对当前档案数据须要用户进一步确认:

2.1.5 非过滤推荐

在tomcat启动过程中会对推荐系统进行初始化,这样在推荐的时候直接能够使用推荐模型进行推荐,这样推荐的时候就不用等待过多时间;
默认使用过滤推荐,非过滤推荐即不使用用户的gender数据对最后的推荐数据进行过滤。
jquery获取是否过滤推荐的checkbox的状态:
$('#filterId').click(function() {	    	    if(this.checked){	    	filter=true;	    }else{	    	filter=false;	    }	    console.info("filter:"+filter);	});
推荐相同使用easyUI的datagrid,其js例如以下:
$('#recommendId').datagrid({		border:false,		singleSelect:true,		height:180,		nowrap:false,		pagination:true,//分页控件		pageSize:4,  // 每页记录数,须要和pageList保持倍数关系		pageList:[4,8,12],		rownumbers:true,//行号		url:'rec/rec_getRecommendData.action',		queryParams: {			uid: userIdValue,			filter:filter		},		columns:[[			{field:'uid',title:'用户ID',width:'50'},			{field:'ugender',title:'用户Gender',width:'80'},			{field:'itemid',title:'档案ID',width:'120'},			{field:'pref',title:'档案评分',width:'150'},			{field:'itemgender',title:'档案Gender',width:'100'},		]]		});
这里传入后台的參数中包括filter和uid。filter即是否使用过滤;

2.1.6 过滤推荐

首先,这里使用Mahout的基于用户的协同过滤算法进行推荐(非MR方式)。
其次。这里的过滤规则例如以下:首先计算出用户评价过的档案中的性别的较大值,比方M(men)(即对哪类性别的档案评分比較多),然后在对用户进行推荐的可能档案中不正确非M的进行计算。直接去掉。这样在最后推荐的时候就不会出现非M性别的档案了。

推荐使用Mahout的基于用户的协同过滤算法,同一时候在《Mahout in action》中对这个代码进行了包装,代码例如以下:
package com.fz.service;import java.io.File;import java.io.IOException;import java.util.ArrayList;import java.util.Collection;import java.util.HashMap;import java.util.List;import java.util.Map;import javax.annotation.Resource;import org.apache.mahout.cf.taste.common.Refreshable;import org.apache.mahout.cf.taste.common.TasteException;import org.apache.mahout.cf.taste.impl.common.FastIDSet;import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity;import org.apache.mahout.cf.taste.model.DataModel;import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;import org.apache.mahout.cf.taste.recommender.IDRescorer;import org.apache.mahout.cf.taste.recommender.RecommendedItem;import org.apache.mahout.cf.taste.recommender.Recommender;import org.apache.mahout.cf.taste.similarity.UserSimilarity;import org.springframework.stereotype.Service;import com.fz.dao.BaseDAO;import com.fz.model.Gender;import com.fz.model.RecommendRating;import com.fz.util.GenderRescorer;import com.fz.util.Utils;/** * libimseti 推荐 * 使用《Mahout in action 》第五章代码 * 使用MySQL数据库作为数据源,则算法非常慢 * @author fansy * */@Service("recommend")public class LibimsetiRecommender implements Recommender {	private  Recommender delegate;	private DataModel model;	private FastIDSet men;	private FastIDSet women;	private FastIDSet usersRateMoreMen;	private FastIDSet usersRateLessMen;	@Resource	private BaseDAO
genderDAO; private boolean filter=true; /** * 从数据库中获取DataModel * @return * @throws IOException * @throws TasteException * @throws NumberFormatException */ public LibimsetiRecommender() throws NumberFormatException, TasteException, IOException{ this(localDataModel()); } private static DataModel localDataModel() throws IOException { FileDataModel dataModel = new FileDataModel(new File(Utils.ratingsFile)); return dataModel; } public LibimsetiRecommender(DataModel model) throws TasteException, NumberFormatException, IOException{ UserSimilarity similarity = new EuclideanDistanceSimilarity(model); UserNeighborhood neighborhood = new NearestNUserNeighborhood(4,similarity,model);// 增大n值能够获得很多其它推荐 delegate = new GenericUserBasedRecommender(model,neighborhood,similarity); this.model=model; FastIDSet[] menWomen = GenderRescorer.parseMenWomen(new File(Utils.genderFile)); men = menWomen[0]; women = menWomen[1]; usersRateMoreMen = new FastIDSet(50000); usersRateLessMen = new FastIDSet(50000); } @Override public void refresh(Collection
alreadyRefreshed) { delegate.refresh(alreadyRefreshed); } @Override public List
recommend(long userID, int howMany) throws TasteException { IDRescorer rescorer= null; if(filter){ rescorer=new GenderRescorer(men,women,usersRateMoreMen,usersRateLessMen,userID,model); } return delegate.recommend(userID, howMany, rescorer); } /** * 推荐整合 * @throws TasteException */ public Map
recommend(long userID,int rows,int page,boolean filter) throws TasteException{ this.filter=filter; String gHql = "from Gender g where g.uId=?"; List
recommend = recommend(userID,20); Map
jsonMap = new HashMap
(); List
tmp = new ArrayList
(); RecommendRating rating =null; if(recommend.size()<=0){ rating = new RecommendRating(); rating.setUid(-1); rating.setUgender('U'); rating.setItemid(-1); rating.setItemgender('U'); rating.setPref(-1); tmp.add(rating); jsonMap.put("total", recommend.size()); jsonMap.put("rows", tmp); return jsonMap; } List
recommendRatings = new ArrayList
(); char uGender = genderDAO.get(gHql, new Object[]{(int)userID}).getGender(); for(RecommendedItem re:recommend){ rating = new RecommendRating(); rating.setUid(userID); rating.setUgender(uGender); rating.setItemid(re.getItemID()); rating.setItemgender(genderDAO.get(gHql, new Object[]{(int)re.getItemID()}).getGender()); rating.setPref(re.getValue()); recommendRatings.add(rating); } for(int i=(page-1)*rows;i
recommend(long userID, int howMany, boolean includeKnownItems) throws TasteException { return delegate.recommend(userID, howMany, includeKnownItems); } @Override public List
recommend(long userID, int howMany, IDRescorer rescorer) throws TasteException { return delegate.recommend(userID, howMany, rescorer); } @Override public List
recommend(long userID, int howMany, IDRescorer rescorer, boolean includeKnownItems) throws TasteException { return delegate.recommend(userID, howMany, rescorer, includeKnownItems); } @Override public float estimatePreference(long userID, long itemID) throws TasteException { IDRescorer rescorer= new GenderRescorer(men,women, usersRateMoreMen,usersRateLessMen,userID,model); return (float)rescorer.rescore(userID, itemID); } @Override public void setPreference(long userID, long itemID, float value) throws TasteException { delegate.setPreference(userID, itemID, value); } @Override public void removePreference(long userID, long itemID) throws TasteException { delegate.removePreference(userID, itemID); } @Override public DataModel getDataModel() { return delegate.getDataModel(); } public Recommender getDelegate() { return delegate; } public void setDelegate(Recommender delegate) { this.delegate = delegate; } public DataModel getModel() { return model; } public void setModel(DataModel model) { this.model = model; } public FastIDSet getMen() { return men; } public void setMen(FastIDSet men) { this.men = men; } public FastIDSet getWomen() { return women; } public void setWomen(FastIDSet women) { this.women = women; } public FastIDSet getUsersRateMoreMen() { return usersRateMoreMen; } public void setUsersRateMoreMen(FastIDSet usersRateMoreMen) { this.usersRateMoreMen = usersRateMoreMen; } public FastIDSet getUsersRateLessMen() { return usersRateLessMen; } public void setUsersRateLessMen(FastIDSet usersRateLessMen) { this.usersRateLessMen = usersRateLessMen; } public boolean isFilter() { return filter; } public void setFilter(boolean filter) { this.filter = filter; } }
代码分析:
1. 初始化时首先会调用localDataModel方法。这种方法用于初始化数据模型。我曾试过使用mysqlDataSource做为数据源,可是计算太慢了。

2. 带參数的LibimsetiRecommender构造方法,就是主要的推荐代码了创建UserSimilarity、UserNeighborhood对象,这里的n值(代码中为4)能够依据自己的须要进行调整,原书中为2。同一时候在这个构造方法中还对gender数据进行了读取,把数据放入内存,方便依据用户ID查询性别。
3. 推荐使用recommend(int userid ,int howmany)就可以,这里代码使用的howmany固定为20。同一时候因为数据须要传入前台。同一时候考虑到分页。所以写了一个recommend(long userID,int rows,int page,boolean filter)方法。用于进行数据分页处理。
4. 在recommend(int userid,int howmany)中假设使用了过滤,那么就初始化IDRescorer为GenderRescorer,当中GenderRescorer为自己定义过滤器,这里须要注意代码清单 Listing5.4 Gender-based rescoring Implementation中的代码有一个地方有问题。原版为:
public boolean isFiltered(long id) {		// TODO Auto-generated method stub		return filterMen?

men.contains(id):women.contains(id); }

须要改为:
public boolean isFiltered(long id) {		// TODO Auto-generated method stub		return filterMen?

(!men.contains(id)):(!women.contains(id)); }

isFiltered方法其解释为 
true
to exclude,
false
otherwise,这个解释和代码是不一样的。

2.1.7 过滤推荐和非过滤推荐对照

比方针对用户ID为8的用户,其过滤推荐为:
这里事实上现实的是没有推荐。再看非过滤推荐:
这里能够看到有3个推荐,可是假设对用户ID为8的用户使用非过滤推荐,那么能够看到这个用户可能是GAY,可是从用户8的评分数据来看。其对F(Female)的档案评分比較多。这说明这3个推荐是不合理的。须要过滤,那么过滤推荐就能够过滤掉这三个推荐数据了。

2.1.8 匿名推荐

待更新。

2.2 文件夹维护

2.2.1 文件夹改动

点击导航配置Tab,能够看到文件夹维护的界面:
这里的操作里面的按钮,使用以下的方式生成:
$(function() {		$('#catalogId')				.datagrid(						{							border : false,							fitColumns : true,							singleSelect : true,							width : 600,							height : 250,							nowrap : false,							fit : true,							pagination : true,// 分页控件							pageSize : 4, // 每页记录数,须要和pageList保持倍数关系							pageList : [ 4, 8, 12 ],							rownumbers : true,// 行号							pagePosition : 'top',							url : 'catalog/catalog_getTreeData.action',							columns : [ [									{										field : 'id',										title : '节点ID',										width : '40'									},									{										field : 'text',										title : '节点名称',										width : '120'									},									{										field : 'url',										title : 'URL',										width : '150'									},									{										field : 'pid',										title : '父节点ID',										width : '60'									},									{										field : 'iconCls',										title : '图标',										width : '100'									},									{										field : 'opt',										title : '操作',										width : '40',										formatter : function(value, row, index) {											var btn_edit = '';											var btn_remove = '';											return btn_edit + btn_remove;										}									} ] ]						});			});

2.2.1 文件夹加入

点击加入按钮,能够对文件夹进行加入,其界面例如以下:

当中。图标使用combobox,其图标加入代码例如以下:

$('#iconId').combobox(				{					formatter : function(row) {						var imageFile = 'themes/icons/' + row.icon;						console.info('imageFile' + imageFile);						return '  '								+ row.text + '';					}				});

分享,成长。快乐

转载请注明blog地址:

你可能感兴趣的文章
Android 进程和服务相关资料
查看>>
线程优先级
查看>>
在ubuntu14.04上使用ambari搭建hadoop集群
查看>>
[paramiko] recv的时候总是出现timeout的错误
查看>>
Spark算子:RDD基本转换操作(6)–zip、zipPartitions
查看>>
【就是快】10分钟搭建一台web服务器!
查看>>
kickstart无人值守安装系统
查看>>
flashbuilder4.7破解
查看>>
开始我的IT职业生涯
查看>>
Nginx_PHP_PHP-FPM网站环境部署手册
查看>>
Spring aop 配置和使用
查看>>
简单栈溢出
查看>>
我的友情链接
查看>>
MySql启动预编译功能
查看>>
欧拉定理
查看>>
EventLoop(netty源码死磕4)
查看>>
#17 HABTM Checkboxes
查看>>
如何修改WordPress自带标签云小工具的显示参数
查看>>
在MyEclipse下使用JUnit
查看>>
Hashtable的使用
查看>>